package com.tmt.paymentcenter.web.configure.security;

import com.tmt.paymentcenter.web.configure.security.bean.ClientResources;
import com.tmt.paymentcenter.web.configure.security.bean.LogoutHandlerImpl;
import com.tmt.paymentcenter.web.configure.security.bean.TmtClientSessionRegistry;
import com.tmt.paymentcenter.web.configure.security.bean.TmtRegisterSessionAuthenticationStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.security.oauth2.resource.PrincipalExtractor;
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter;
import org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
import org.springframework.security.web.authentication.session.*;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.filter.CompositeFilter;
import tmt.usercenter.web.domain.security.UserDetailsImpl;

import javax.servlet.Filter;
import javax.servlet.http.HttpSessionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Configuration
@EnableOAuth2Client
@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true, securedEnabled = true) // 开启全局方法安全验证
/*@Order(1)*/
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	@Qualifier("oauth2ClientContext")
	@Autowired
	private OAuth2ClientContext oauth2ClientContext;

	@Value("${security.oauth2.client.client-id}")
	private String clientId;

	@Value("${security.oauth2.client.client-secret}")
	private String clientSecret;

	@Value("${security.oauth2.client.check-token-uri}")
	private String checkTokenUri;

	@Autowired
	private LogoutHandlerImpl logoutHandlerImpl;

	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

	@Bean
	public RestTemplate restTemplate(ClientHttpRequestFactory factory){
		return new RestTemplate(factory);
	}

	@Bean
	public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
		SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
		factory.setReadTimeout(5000);//ms
		factory.setConnectTimeout(15000);//ms
		return factory;
	}

	@Bean
	public ServletListenerRegistrationBean httpSessionEventPublisher() {
		return new ServletListenerRegistrationBean<HttpSessionListener>(new HttpSessionEventPublisher());
	}

	@Primary
	@Bean
	public SessionRegistry sessionRegistry() {
		return new TmtClientSessionRegistry();
	}

	@Bean
	@Order(1)
	public ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlAuthenticationStrategy(){
		ConcurrentSessionControlAuthenticationStrategy cscas= new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());
		cscas.setMaximumSessions(1);
		cscas.setExceptionIfMaximumExceeded(true);
		return cscas;
	}

	@Bean
	@Order(2)
	public SessionFixationProtectionStrategy sessionFixationProtectionStrategy(){
		return new SessionFixationProtectionStrategy();
	}

	@Bean
	@Order(3)
	public RegisterSessionAuthenticationStrategy registerSessionAuthenticationStrategy(){
		return new TmtRegisterSessionAuthenticationStrategy((TmtClientSessionRegistry) sessionRegistry());
	}

	@Bean
	public CompositeSessionAuthenticationStrategy compositeSessionAuthenticationStrategy(){
		List<SessionAuthenticationStrategy> sessionAuthenticationStrategies = new ArrayList<>();
		sessionAuthenticationStrategies.add(concurrentSessionControlAuthenticationStrategy());
		sessionAuthenticationStrategies.add(sessionFixationProtectionStrategy());
		sessionAuthenticationStrategies.add(registerSessionAuthenticationStrategy());
		return new CompositeSessionAuthenticationStrategy(sessionAuthenticationStrategies);
	}

	@Bean
	@ConfigurationProperties("github")
	public ClientResources github() {
		return new ClientResources();
	}

	@Bean
	@ConfigurationProperties("security.oauth2")
	public ClientResources oauth2Client() {
		return new ClientResources();
	}

	@Bean
	public FilterRegistrationBean oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
		FilterRegistrationBean registration = new FilterRegistrationBean();
		registration.setFilter(filter);
		registration.setOrder(-100);
		return registration;
	}

	@Bean
	public PrincipalExtractor principalExtractor() {
		return new PrincipalExtractor() {
			@Override
			public Object extractPrincipal(Map<String, Object> map) {
				return new UserDetailsImpl(map);
			}
		};
	}

	@Override
	public void configure(WebSecurity web) throws Exception {
		web.debug(true);

		// 过滤对应匹配路径下的请求，优于 HttpSecurity 中用拦截路径来过滤
		web.ignoring().antMatchers(
				"/static/asset/**",
				"/**/*.ico",
				"/asset/js/**",
				"/error",
				"/error/**"
		);
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.sessionManagement()
				.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
				.sessionFixation().migrateSession()
				.sessionAuthenticationStrategy(compositeSessionAuthenticationStrategy())
				;

		http.authorizeRequests()
				.antMatchers("/register**", "/forgot**", "/me**",
						"/error**", "/external/logout/**").permitAll()
				.anyRequest().authenticated() //其他未匹配的所有请求都需要用户被认证

				.and().formLogin() //允许用户基于表单认证
				.loginPage("/login") //定义认证的URL地址，默认为/login
				.defaultSuccessUrl("/c/") //登录成功后用户进入首页
				.permitAll()

//				.and().httpBasic() //允许用户使用HTTP基本验证进行认证*/

				//指定登出跳转地址
				.and().logout()
				.logoutUrl("/logout") //设置登出地址，默认为/logout
				.addLogoutHandler(logoutHandlerImpl)
				.logoutSuccessUrl("/me") //设置登出后的跳转地址，默认为/login
				.permitAll()

				.and().exceptionHandling().accessDeniedPage("/error/denied")
				//.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/"))

				//防跨站请求伪造
				.and().csrf().disable()
				//.and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())

				.addFilterBefore(ssoFilter(), (Class<? extends Filter>)BasicAuthenticationFilter.class)
		;
	}

	/*private static CsrfTokenRepository csrfTokenRepository() {
		HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
		repository.setHeaderName("X-XSRF-TOKEN");
		return repository;
	}*/

	private Filter ssoFilter() {
		CompositeFilter filter = new CompositeFilter();
		List<Filter> filters = new ArrayList<>();
		filters.add(ssoFilter(oauth2Client(), "/login"));
		filter.setFilters(filters);
		return filter;
	}

	private Filter ssoFilter(ClientResources client, String path) {
		OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(path);
		OAuth2RestTemplate template = new OAuth2RestTemplate(oauth2Client().getClient(), oauth2ClientContext);
		filter.setRestTemplate(template);

		UserInfoTokenServices tokenServices = new UserInfoTokenServices(
				client.getResource().getUserInfoUri(), client.getClient().getClientId());
		tokenServices.setRestTemplate(template);
		tokenServices.setPrincipalExtractor(principalExtractor());

		filter.setSessionAuthenticationStrategy(registerSessionAuthenticationStrategy());
		filter.setTokenServices(tokenServices);
		return filter;
	}

}
